package preka.prefuse.action;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import prefuse.Visualization;
import prefuse.action.GroupAction;
import prefuse.data.expression.Predicate;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.VisiblePredicate;
import preka.IVC;
import preka.prefuse.visual.VisualInstance;
import preka.prefuse.visual.VisualInstances;

/**
 * TODO escrever descrição e texto métodos
 * {@link #setFilterPredicate(Predicate)}
 *
 * @author <a href="http://vhbarros.org">Vitor Hugo Barros</a>
 */
public abstract class Heuristic extends GroupAction {

    /** A reference to filtering predicate for this Action */
    protected IVC m_IVC;
    protected int [] m_visualClusterMap;
    protected String [] m_classNamesMap;
    protected Random m_random;

    /**
     * Create a new Heuristic that processes all groups.
     * @param vis the {@link prefuse.Visualization} to process
     * @see Visualization#ALL_ITEMS
     */
    public Heuristic(IVC ivc) {
        this(ivc, Visualization.ALL_ITEMS);
    }

    /**
     * Create a new Heuristic that processes the specified group.
     * @param group the name of the group to process
     * @param vinsts visual instances associated with the IVC process
     */
    public Heuristic(IVC ivc, String group) {
        super(ivc.getVisualization(), group);
        m_IVC = ivc;
        try {
            m_visualClusterMap = new int[m_IVC.getNumClasses()];
            m_classNamesMap = new String[m_IVC.getNumClasses()];
        } catch (Exception ex) {
            Logger.getLogger(Heuristic.class.getName()).log(Level.SEVERE, null, ex);
        }
        m_random = new Random();
    }

    // ------------------------------------------------------------------------

    /**
     * @see prefuse.action.Action#run(double)
     */
    public void run(double frac) {

        m_IVC.incrementIteration();
        m_IVC.getVisualInstances().updatePositions(m_IVC.getVisualization(), IVC.GRAPH_GROUP);

        // Não efectua qualquer acção na 1ª iteracção para deixar estabilizar o layout
        if(m_IVC.getIteration() == 1)
            return;

        // Os primeiros movimentos são para definir os grupos
        if(m_IVC.getNumMovedInstances() < m_IVC.getNumClasses())
            seedNode();

        // Assim que todas os grupos estejam definido, deve ser efectuado o mapeamento com os grupos visuais
        //if(m_IVC.getNumMovedInstances() == m_IVC.getNumClasses())
            //mapVisualClusters();

        // Movimentos posteriores
        if(m_IVC.getNumMovedInstances() >= m_IVC.getNumClasses()) {
            mapVisualClusters(); // Necessário em todas as iteracções (os visual clusters podem mudar!)
            moveNode();
        }

        //System.out.println("Hey! frac:"+frac);
    }

    // TODO Melhoria: só suporta até 4 grupos!
    protected void seedNode() {
        int numMovedInstances = m_IVC.getNumMovedInstances();
        double [] pos = getRandomClusterPosition(numMovedInstances);
        m_classNamesMap[numMovedInstances] = m_IVC.getClassName(numMovedInstances);
        
        moveInstance(getRandomNode(m_classNamesMap[numMovedInstances]), pos[0], pos[1]);
    }

    // TODO Melhoria: só suporta até 4 grupos!
    protected double[] getRandomClusterPosition(int cluster) {
        double [] pos = new double[2];
        double widthx = m_IVC.getDisplay().getWidth()/0.75/2;
        double heighty = m_IVC.getDisplay().getHeight()/0.75/2;
        double mlrandx = m_random.nextDouble()*m_IVC.getMustLinkDistance();
        double mlrandy = m_random.nextDouble()*m_IVC.getMustLinkDistance();
        //System.out.println("mlrandx="+mlrandx+" mlrandy="+mlrandy);

        switch(cluster) {
            case 0:
                pos[0] = widthx - mlrandx;
                pos[1] = heighty - mlrandy;
                break;
            case 1:
                pos[0] = -1*(widthx - mlrandx);
                pos[1] = -1*(heighty - mlrandy);
                break;
            case 2:
                pos[0] = widthx - mlrandx;
                pos[1] = -1*(heighty - mlrandy);
                break;
            case 3:
                pos[0] = -1*(widthx - mlrandx);
                pos[1] = heighty - mlrandy;
                break;
            default:
                break;
        }

        return pos;
    }

    // TODO Melhoria: só suporta até 4 grupos!
    protected void mapVisualClusters() {
        Iterator items = m_vis.items(IVC.NODES_GROUP);
        while(items.hasNext()) {
            VisualItem vi = (VisualItem) items.next();
            if (vi.getInt(IVC.INSTANCE_ATTRIBUTE) < 0) {
                if (vi.getEndX() > 0 && vi.getEndY() > 0)
                    m_visualClusterMap[0] = vi.getInt(IVC.INSTANCE_ATTRIBUTE);
                if (vi.getEndX() < 0 && vi.getEndY() < 0)
                    m_visualClusterMap[1] = vi.getInt(IVC.INSTANCE_ATTRIBUTE);
                if (vi.getEndX() > 0 && vi.getEndY() < 0)
                    m_visualClusterMap[2] = vi.getInt(IVC.INSTANCE_ATTRIBUTE);
                if (vi.getEndX() < 0 && vi.getEndY() > 0)
                    m_visualClusterMap[3] = vi.getInt(IVC.INSTANCE_ATTRIBUTE);
            }
        }
        //printClusterMappings();
    }

    protected int getHeuristicCluster(int visualCluster) {
        for (int i=0 ; i < m_visualClusterMap.length ; i++)
            if (m_visualClusterMap[i] == visualCluster)
                return i;
        return visualCluster;
    }

    protected int getHeuristicCluster(String className) {
        for (int i=0 ; i < m_classNamesMap.length ; i++)
            if (m_classNamesMap[i].equals(className))
                return i;
        return -9999;
    }

    protected String getHeuristicClassName(int visualCluster) {
        return m_classNamesMap[getHeuristicCluster(visualCluster)];
    }

    public void printClusterMappings() {
        for (int i=0 ; i < m_visualClusterMap.length ; i++)
            System.out.println("Heurística: "+i+" Visual:"+m_visualClusterMap[i]+" Nome:"+m_classNamesMap[i]);
    }


    protected VisualInstance getRandomNode(String className) {

        ArrayList notMovedVisualInstances =  m_IVC.getVisualInstances().getNotMovedVisualInstances(m_IVC.getVisualization(),
                                                                                                   IVC.NODES_GROUP,
                                                                                                   className);

        m_random = new Random();
        int index = m_random.nextInt (notMovedVisualInstances.size());
        
        return (VisualInstance) notMovedVisualInstances.get(index);

    }

    protected void moveInstance(VisualInstance vinst, double x, double y) {
        VisualItem vi = m_IVC.getVisualInstances().getVisualItem(m_IVC.getVisualization(),
                                                                 IVC.NODES_GROUP,
                                                                 vinst.getNumInstance());
        System.out.println("move instância "+vinst.getNumInstance()+" visualItem:"+vi.getInt(IVC.INSTANCE_ATTRIBUTE)+
                           " de ("+vi.getX()+","+vi.getY()+") para ("+x+","+y+")");
        /*if (m_IVC.getLayoutMethod() == IVC.NO_LAYOUT) {
            vi.setX(y);
            vi.setY(y);
            vi.setFixed(true);
            vinst.setHasMoved(true);
        } else {*/
            vi.setStartX(vi.getX());
            vi.setStartY(vi.getY());
            vi.setEndX(x);
            vi.setEndY(y);
            vi.setFixed(true);

            vinst.setIsMoving(true);
            m_IVC.getVisualization().run(IVC.HEURISTIC_ANIMATOR);
        //}
    }

    protected abstract void moveNode();



} // end of class Heuristic
